iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
AI 自動化

用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文系列 第 9

Day 9|分類 Prompt 設計:固定分類、2–4 個標籤、2–3 句摘要

  • 分享至 

  • xImage
  •  

昨天講完 Jina Reader 怎麼把網址變成乾淨的純文字、又為什麼截斷在 6000 字,今天要接著往下:這段文字最後是怎麼變成 system prompt,丟給 AI 換回分類、標籤跟摘要的。

先看 classify() 這個函式,怎麼組出 system prompt:

async function classify(env, { url, content, userNote, existingTags }) {
  const system = [
    '你是一個個人知識庫的分類助手。',
    '請閱讀使用者提供的網頁內容,輸出繁體中文的分類結果。',
    `分類(category)必須從以下選項擇一:${CATEGORIES.join('、')}`,
    '標籤(tags)請給 2-4 個精準的關鍵字,不要太籠統。',
    existingTags?.length
      ? `已經用過的標籤(優先從中挑選相同概念的詞,避免同義詞發散,例如已有「AI應用」就不要再造「AI工具」):${existingTags.join('、')}`
      : null,
    '只有既有標籤都不貼切時,才建立新標籤。',
    '摘要(summary)請用 2-3 句話寫出這篇的重點,讓人日後掃一眼就知道值不值得回頭讀。',
    '只輸出 JSON,不要有任何前言、說明或 markdown 標記。',
    '格式:{"title": string, "category": string, "tags": string[], "summary": string}',
  ]
    .filter((l) => l !== null)
    .join('\n');
  // ...
}

這段程式碼有一個我覺得很有意思的地方,是 system prompt 不是一整塊寫死的字串,而是一個陣列,每一行規則各自佔一個元素,最後才 join('\n') 接起來。這樣寫的好處是 existingTags?.length 那一行可以直接用三元運算子插進去,沒有既有標籤就整行變 null,交給 filter((l) => l !== null) 濾掉。不用寫一堆字串拼接或範本字面值的條件判斷,規則要增減也是陣列裡加一行、拿掉一行,改起來很乾脆。

接下來是規則本身。分類(category)那一行寫得很硬:「必須從以下選項擇一」,CATEGORIES 是寫死的七個分類,投資理財、技術開發、教學課程、英文學習、工具資源、旅遊、生活其他,跟 Day 5 提過 Notion 的 Select 欄位是同一份清單。標籤(tags)那一行語氣完全不同,只說「請給 2-4 個精準的關鍵字,不要太籠統」,沒有畫出一個可選清單。這兩行擺在一起看就很清楚:分類要少、要穩,這樣才能一直拿來當長期的分類軸使用;標籤本來就是要越用越多、越用越細,拿去描述同一篇文章裡分類軸涵蓋不到的細節,兩者角色不一樣,prompt 的寫法也就故意不對稱。

2-4 個標籤這個區間也不是隨口訂的。給 1 個標籤等於沒分類,多半只是把分類欄位的內容再講一次;沒設上限的話,免費模型有時候會很興奮地一次生出七八個標籤,Multi-select 欄位很快就會塞滿一堆只出現過一次的詞,之後想篩選反而更亂。2-4 這個範圍算是抓了一個「夠用來描述細節,又不會讓標籤庫發散太快」的中間值。至於 prompt 裡那句「已經用過的標籤,優先從中挑選相同概念的詞」,是為了同一個道理再往前擋一層——不過這部分怎麼做的、AI 又常常怎麼陽奉陰違,留到 Day 11 整天講。

摘要(summary)那一行寫「2-3 句話寫出這篇的重點,讓人日後掃一眼就知道值不值得回頭讀」,這句話與其說是格式要求,不如說是把 Day 1 講的那個問題直接寫進 prompt 裡了:存起來的連結會被忘記,就是因為存的時候沒有留下「為什麼存」的線索。摘要要短,是因為它的讀者是未來某天滑 Notion 的自己,不是要看完整篇文章的重述,只是要有夠拿來判斷值不值得點開的一句話。字數訂在 2-3 句而不是「越短越好」,是留一點空間讓 AI 可以把重點跟一句補充講完,太短的話常常只剩下標題重講一次,什麼資訊量都沒有。

最後兩行是格式規則:「只輸出 JSON,不要有任何前言、說明或 markdown 標記」,加上直接寫死 {"title": string, "category": string, "tags": string[], "summary": string} 這個格式範本。這行寫得這麼明確是有原因的——免費模型很愛在 JSON 前後加一句「好的,以下是分類結果:」,或者把整包包在 ```json 的 code fence 裡,這兩種習慣都會讓後面的 JSON.parse 直接爆掉。先在 prompt 裡把話講死能擋掉大部分情況,但擋不掉全部,AI 沒乖乖聽話、程式要怎麼接住這些例外,是明天的主題。

呼叫 AI 的地方,callMistralcallOpenRouter 兩支函式都把 temperature 設成 0.2

body: JSON.stringify({
  model: 'mistral-medium-latest',
  messages: [
    { role: 'system', content: system },
    { role: 'user', content: user },
  ],
  temperature: 0.2,
}),

分類這件事要的是穩定、一致,不是創意,同一篇文章今天分進「技術開發」、明天心血來潮分進「工具資源」,對一個要長期累積的知識庫來說是災難。溫度壓低,就是希望同樣的輸入盡量換回同樣的輸出,這點跟免費模型會不會掛掉是兩件事,這裡先按下不表,Day 12 會整天講三層 fallback 怎麼設計的。

今天算是把 system prompt 這塊拆開來看完了:陣列拼字串的寫法、分類為什麼固定又跟標籤角色分工、2-4 個標籤跟 2-3 句摘要這兩個數字怎麼來的。明天要看的是反過來的問題:AI 沒照著這份規格乖乖回話的時候,程式要怎麼接住那個爛掉的回應。


上一篇
Day 8|用 Jina Reader 抓網頁內文,以及為什麼要截斷在 6000 字
系列文
用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言